/*
 * CHttpServer.cpp
 *
 *  Created on: 2016-7-25
 *      Author: terry
 */

#include "CHttpServer.h"
#include "TStringUtil.h"
#include "TStringCast.h"
#include <iostream>
#include <fstream>


#define USER_DIGEST_FILE "user.htpasswd"
#define HTTP_DOMAIN		 "HttpServer"


static bool mg_startswith(const struct mg_str& src, const struct mg_str& sub)
{
	if (src.len < sub.len)
	{
		return false;
	}

	for (size_t i = 0; i < sub.len; i++)
	{
		if (src.p[i] != sub.p[i])
		{
			return false;
		}
	}
	return true;
}


static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpServer* pThis = (CHttpServer*)nc->mgr->user_data;
	if (ev == MG_EV_HTTP_REQUEST)
	{
		struct http_message *hm = (struct http_message *) ev_data;
		pThis->handleHttpEvent(nc, ev, hm);
	}
    else if (ev == MG_EV_TIMER)
    {
        //
    }
    else if (ev == MG_EV_SEND)
    {
        //
    }
}

struct file_writer_data {
  FILE *fp;
  size_t bytes_written;
  std::string filepath;
};

static void handle_upload(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpServer* pThis = (CHttpServer*)nc->mgr->user_data;
	pThis->handleUpload(nc, ev, ev_data);

}

static void handle_direntry(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpServer* pThis = (CHttpServer*)nc->mgr->user_data;
	pThis->handleDirEntry(nc, ev, ev_data);
}


struct ValueName
{
    int value;
    const char* name;
};

static ValueName s_codeNames[] =
{
    {200, "OK"},
    {400, "Bad Request"},
    {401, "Unauthorized"},
    {402, "Payment Required"},
    {403, "Forbidden   "},
    {404, "Not Found   "},
    {405, "Method Not Allowed"},
    {406, "Not Acceptable"},
    {407, "Proxy Authentication Required"},
    {408, "Request Timeout"},
    {410, "Gone        "},
    {411, "Length Required"},
    {412, "Precondition Failed"},
    {413, "Request Entity Too Large"},
    {414, "Request-URI Too Long"},
    {415, "Unsupported Media Type"},
    {451, "Invalid parameter"},
    {452, "Illegal Conference Identifier"},
    {453, "Not Enough Bandwidth"},
    {454, "Session Not Found"},
    {455, "Method Not Valid In This State"},
    {456, "Header Field Not Valid"},
    {457, "Invalid Range   PLAY"},
    {458, "Parameter Is Read-Only"},
    {459, "Aggregate Operation Not Allowed"},
    {460, "Only Aggregate Operation Allowed"},
    {461, "Unsupported Transport"},
    {462, "Destination Unreachable"},
    {500, "Internal Server Error"},
    {501, "Not Implemented"},
    {502, "Bad Gateway "},
    {503, "Service Unavailable"},
    {504, "Gateway Timeout"},
    {505, "RTSP Version Not Supported"},
    {551, "Option not support"},
};

static const char* getCodeName(int code)
{
    for (size_t i = 0; i < sizeof(s_codeNames)/sizeof(s_codeNames[0]); i ++)
    {
        if (code == s_codeNames[i].value)
        {
            return s_codeNames[i].name;
        }
    }
    return "";
}



CHttpServer::CHttpServer():
	m_handler(),
	m_mgr(),
	m_opts(),
	m_connection()
{
	mg_mgr_init(&m_mgr, this);

	m_webRoot = ".";
	m_opts.document_root = m_webRoot.c_str();

	m_userFile = USER_DIGEST_FILE;
	m_opts.auth_domain = HTTP_DOMAIN;
	m_opts.global_auth_file = m_userFile.c_str();

	m_uploadDir = ".";



	setupUser();
}

CHttpServer::~CHttpServer()
{
}

bool CHttpServer::setWebRoot(const char* dirPath)
{
	if (!dirPath || strlen(dirPath) <= 0)
	{
		return false;
	}

	m_webRoot = dirPath;

	m_opts.document_root = m_webRoot.c_str();

	return true;
}

bool CHttpServer::setUploadDir(const char* dirPath)
{
	if (!dirPath || strlen(dirPath) <= 0)
	{
		return false;
	}

	m_uploadDir = dirPath;

	return true;
}

bool CHttpServer::setUserFile(const char* filePath)
{
	if (!filePath || strlen(filePath) <= 0)
	{
		m_userFile.clear();
		m_opts.global_auth_file = NULL;
	}
	else
	{
		m_userFile = filePath;
		m_opts.global_auth_file = m_userFile.c_str();
	}

	return true;
}

bool CHttpServer::addDir(const char* url, const char* dirPath)
{
	if (strlen(url) == 0)
	{
		return false;
	}

	if (strlen(dirPath) == 0)
	{
		return false;
	}

	DirEntry entry;
	if (findDirEntry(url, entry))
	{
		return false;
	}

	entry.dirPath = dirPath;
	entry.url = url;
	entry.opts.document_root = entry.dirPath.c_str();

	m_entryMap[url] = entry;

	return true;
}

bool CHttpServer::start(int port, HttpHandler* handler)
{
	if (port < 0 || !handler)
	{
		return false;
	}

	m_handler = handler;
	m_port = comn::StringCast::toString(port);

	m_connection = mg_bind(&m_mgr, m_port.c_str(), ev_handler);
	if (!m_connection)
	{
		printf("can not bind port : %d\n, exit !!!", port);
		exit(1);
		return false;
	}

	mg_set_protocol_http_websocket(m_connection);

	mg_register_http_endpoint(m_connection, "/upload", handle_upload);


	DirEntryMap::iterator it = m_entryMap.begin();
	for (; it != m_entryMap.end(); ++ it)
	{
		mg_register_http_endpoint(m_connection, it->first.c_str(), handle_direntry);
	}

	return comn::Thread::start();
}

void CHttpServer::stop()
{
	if (isRunning())
	{
		comn::Thread::stop();
	}

	mg_mgr_free(&m_mgr);
}

bool CHttpServer::isStarted()
{
	return comn::Thread::isRunning();
}

void CHttpServer::handleHttpEvent(struct mg_connection *nc, int ev, struct http_message *hm)
{
	static const struct mg_str api_prefix = mg_mk_str("/api");
	static const struct mg_str upload_prefix = mg_mk_str("/upload");
	static const struct mg_str sys_prefix = mg_mk_str("/sys/");
	//static const struct mg_str sys_prefix = mg_mk_str("//");

	if (mg_startswith(hm->uri, api_prefix))
	{
		mg_str uri;
		uri.p = hm->uri.p + api_prefix.len;
		uri.len = hm->uri.len - api_prefix.len;
        handleHttpApi(nc, hm, uri);
	}
	else if (mg_startswith(hm->uri, upload_prefix))
	{
	}
	else if (mg_startswith(hm->uri, sys_prefix))
	{
		mg_str uri;
		uri.p = hm->uri.p + (sys_prefix.len - 1);
		uri.len = hm->uri.len - (sys_prefix.len - 1);

		handleSysApi(nc, hm, uri);
	}
	else
	{
		mg_serve_http(nc, hm, m_opts);
	}
}

int CHttpServer::run()
{
	while (!m_canExit)
	{
		mg_mgr_poll(&m_mgr, 10000);
	}
	return 0;
}

void CHttpServer::doStop()
{
	/// connect server
	std::string addr("127.0.0.1:");
	addr += m_port;

	mg_connect(&m_mgr, addr.c_str(), NULL);
}

void CHttpServer::handleHttpApi(struct mg_connection* nc, struct http_message *hm, mg_str& str)
{
	int code = 404;
    std::string json = "{}";
	if (m_handle)
	{
		int ret = m_handler->handleRest(hm->method, str, hm->query_string, hm->body, json);
		if (ret == 0)
		{
			code = 200;
		}
		else
		{
			code = 500;
		}
	}

	sendJson(nc, code, NULL, json);
}

void CHttpServer::sendJson(mg_connection *nc, int code, const char *reason, const std::string& json)
{
	if (reason == NULL)
	{
		reason = getCodeName(code);
	}

	mg_printf(nc,   "HTTP/1.1 %d %s\r\n"
					"Cache-Control: no-cache\r\n"
					"Content-Type: application/json\r\n"
					"Access-Control-Allow-Origin: *\r\n"
					"Content-Length: %d\r\n\r\n%s",
					code, reason,
					json.size(), json.c_str());

}

void CHttpServer::handleUpload(struct mg_connection *nc, int ev, void *ev_data)
{
	struct file_writer_data *data = (struct file_writer_data *) nc->user_data;
	struct mg_http_multipart_part *mp = (struct mg_http_multipart_part*)ev_data;

    if (ev == MG_EV_HTTP_PART_BEGIN)
    {
    	if (!data)
    	{
    		data = new file_writer_data();
    		data->filepath = makeUploadPath(mp->file_name);
    		data->fp = fopen(data->filepath.c_str(), "wb");
    		data->bytes_written = 0;

            if (data->fp == NULL)
            {
            	mg_printf(nc, "%s",
                        "HTTP/1.1 500 Failed to open a file\r\n"
                        "Content-Length: 0\r\n\r\n");
            	nc->flags |= MG_F_SEND_AND_CLOSE;
            	return;
            }
            nc->user_data = (void *) data;
    	}
    }
    else if (ev == MG_EV_HTTP_PART_DATA)
    {
		if (fwrite(mp->data.p, 1, mp->data.len, data->fp) != mp->data.len)
		{
			mg_printf(nc, "%s",
				"HTTP/1.1 500 Failed to write to a file\r\n"
				"Content-Length: 0\r\n\r\n");
			nc->flags |= MG_F_SEND_AND_CLOSE;
			return;
		}
		data->bytes_written += mp->data.len;
    }
    else if (ev == MG_EV_HTTP_PART_END)
    {
    	mg_printf(nc,
				"HTTP/1.1 200 OK\r\n"
				"Content-Type: text/plain\r\n"
				"Connection: close\r\n\r\n"
				"Written %ld of POST data to file:%s\n\n",
				(long) ftell(data->fp),
				data->filepath.c_str());
    	nc->flags |= MG_F_SEND_AND_CLOSE;
    	fclose(data->fp);

    	std::string filepath(data->filepath);

    	delete data;
    	nc->user_data = NULL;

    	if (m_handler)
    	{
    		m_handler->handleUpload(mp->var_name, filepath);
    	}
    }
    else if (ev == MG_EV_HTTP_MULTIPART_REQUEST)
    {

    }
}

std::string	CHttpServer::makeUploadPath(const char* filename)
{
	std::string fullpath = m_uploadDir;
	fullpath += '/';
	fullpath += filename;
	return fullpath;
}

bool CHttpServer::setupUser()
{
	FILE* f = fopen(m_userFile.c_str(), "rb");
	if (f)
	{
		fclose(f);
		return true;
	}

	return resetUser("admin", "admin");
}

std::string CHttpServer::makeUser(const std::string& name, const std::string& realm, const std::string& passwd)
{
	char buf[33] = {0};
	std::string src = name + ":" + realm + ":" + passwd;
	cs_md5(buf, src.c_str(), src.size(), NULL);
	return buf;
}

bool CHttpServer::resetUser(const std::string& name, const std::string& passwd)
{
	bool done = false;
	FILE* f = fopen(m_userFile.c_str(), "wb");
	if (f)
	{
		std::string md5 = makeUser(name, HTTP_DOMAIN, passwd);
		std::string line(name);
		line += ":";
		line += HTTP_DOMAIN;
		line += ":";
		line += md5;
		fwrite(line.c_str(), 1, line.size(), f);
		fclose(f);
		done = true;
	}
	return done;
}

void CHttpServer::handleSysApi(struct mg_connection* nc, struct http_message *hm, mg_str& str)
{
	int code = 404;
	std::string json = "{}";

	static const struct mg_str password_prefix = mg_mk_str("/password");
	if (mg_vcmp(&str, password_prefix.p) == 0)
	{
		char name[256] = {0};
		char password[256] = {0};
		mg_get_http_var(&hm->body, "user", name, sizeof(name));
		mg_get_http_var(&hm->body, "password", password, sizeof(password));

		resetUser(name, password);

		code = 200;
	}

	sendJson(nc, code, NULL, json);
}

bool CHttpServer::findDirEntry(const char* url, DirEntry& entry)
{
	bool found = false;
	DirEntryMap::const_iterator it = m_entryMap.find(url);
	if (it != m_entryMap.end())
	{
		entry = it->second;
		found = true;
	}
	return found;
}

bool CHttpServer::findDirEntry(struct mg_str& url, DirEntry& entry)
{
	bool found = false;
	DirEntryMap::const_iterator it = m_entryMap.begin();
	for (; it != m_entryMap.end(); ++ it)
	{
		mg_str prefix = mg_mk_str(it->first.c_str());
		if (mg_startswith(url, prefix))
		{
			entry = it->second;
			found = true;
		}
	}
	return found;
}

void CHttpServer::handleDirEntry(struct mg_connection *nc, int ev, void *ev_data)
{
	struct http_message *hm = (struct http_message *)ev_data;

	DirEntry entry;
	if (findDirEntry(hm->uri, entry))
	{
		entry.opts.document_root = entry.dirPath.c_str();
		hm->uri.p = hm->uri.p + entry.url.size();
		hm->uri.len = hm->uri.len - entry.url.size();
		mg_serve_http(nc, hm, entry.opts);
	}
	else
	{
		mg_printf(nc, "%s",
				"HTTP/1.1 404 Not found\r\n"
		        "Content-Length: 0\r\n\r\n");
	}
}